﻿using System;
using System.Reflection.Emit;
using Sigil.Impl;

namespace Sigil
{
	public partial class Emit<DelegateType>
	{
		private TransitionWrapper CheckConvertible(String method, Type toType)
		{
			return
					Wrap(
							new[]
										{
												new StackTransition(new [] { typeof(Int32) }, new [] { toType }),
												new StackTransition(new [] { typeof(NativeIntType) }, new [] { toType }),
												new StackTransition(new [] { typeof(Int64) }, new [] { toType }),
												new StackTransition(new [] { typeof(Single) }, new [] { toType }),
												new StackTransition(new [] { typeof(Double) }, new [] { toType }),
												new StackTransition(new [] { typeof(AnyPointerType) }, new [] { toType }),
												new StackTransition(new [] { typeof(AnyPointerType) }, new [] { toType }),
												new StackTransition(new [] { typeof(AnyPointerType) }, new [] { toType }),
												new StackTransition(new [] { typeof(AnyPointerType) }, new [] { toType }),
												new StackTransition(new [] { typeof(AnyPointerType) }, new [] { toType }),
												new StackTransition(new [] { typeof(AnyByRefType) }, new [] { toType })
										},
							method
					);
		}

		/// <summary>
		/// Convert a value on the stack to the given non-character primitive type.
		///
		/// Primitives are int8, uint8, int16, uint16, int32, uint32, int64, uint64, Single, Double, native Int32 (IntPtr), and unsigned native Int32 (UIntPtr).
		/// </summary>
		public Emit<DelegateType> Convert<PrimitiveType>()
				where PrimitiveType : struct
		{
			return Convert(typeof(PrimitiveType));
		}

		/// <summary>
		/// Convert a value on the stack to the given non-character primitive type.
		///
		/// Primitives are int8, uint8, int16, uint16, int32, uint32, int64, uint64, Single, Double, native Int32 (IntPtr), and unsigned native Int32 (UIntPtr).
		/// </summary>
		public Emit<DelegateType> Convert(Type primitiveType)
		{
			if (primitiveType == null)
			{
				throw new ArgumentNullException("primitiveType");
			}

			if (!primitiveType.IsPrimitive || primitiveType == typeof(Char))
			{
				throw new ArgumentException("Convert expects a non-character primitive type");
			}

			var transitions = CheckConvertible("Convert", primitiveType);

			if (primitiveType == typeof(Byte))
			{
				ConvertToByte(transitions);
				return this;
			}

			if (primitiveType == typeof(sbyte) || primitiveType == typeof(bool)) // bool is an int8 on the stack
			{
				ConvertToSByte(transitions);
				return this;
			}

			if (primitiveType == typeof(Int16))
			{
				ConvertToInt16(transitions);
				return this;
			}

			if (primitiveType == typeof(UInt16))
			{
				ConvertToUInt16(transitions);
				return this;
			}

			if (primitiveType == typeof(Int32))
			{
				ConvertToInt32(transitions);
				return this;
			}

			if (primitiveType == typeof(UInt32))
			{
				ConvertToUInt32(transitions);
				return this;
			}

			if (primitiveType == typeof(Int64))
			{
				ConvertToInt64(transitions);
				return this;
			}

			if (primitiveType == typeof(UInt64))
			{
				ConvertToUInt64(transitions);
				return this;
			}

			if (primitiveType == typeof(IntPtr))
			{
				ConvertToNativeInt(transitions);
				return this;
			}

			if (primitiveType == typeof(UIntPtr))
			{
				ConvertToUnsignedNativeInt(transitions);
				return this;
			}

			if (primitiveType == typeof(Single))
			{
				ConvertToFloat(transitions);
				return this;
			}

			if (primitiveType == typeof(Double))
			{
				ConvertToDouble(transitions);
				return this;
			}

			throw new Exception("Shouldn't be possible");
		}

		/// <summary>
		/// Convert a value on the stack to the given non-character, non-Single, non-Double primitive type.
		/// If the conversion would overflow at runtime, an OverflowException is thrown.
		///
		/// Primitives are int8, uint8, int16, uint16, int32, uint32, int64, uint64, Single, Double, native Int32 (IntPtr), and unsigned native Int32 (UIntPtr).
		/// </summary>
		public Emit<DelegateType> ConvertOverflow<PrimitiveType>()
		{
			return ConvertOverflow(typeof(PrimitiveType));
		}

		/// <summary>
		/// Convert a value on the stack to the given non-character, non-Single, non-Double primitive type.
		/// If the conversion would overflow at runtime, an OverflowException is thrown.
		///
		/// Primitives are int8, uint8, int16, uint16, int32, uint32, int64, uint64, Single, Double, native Int32 (IntPtr), and unsigned native Int32 (UIntPtr).
		/// </summary>
		public Emit<DelegateType> ConvertOverflow(Type primitiveType)
		{
			if (primitiveType == null)
			{
				throw new ArgumentNullException("primitiveType");
			}

			if (!primitiveType.IsPrimitive || primitiveType == typeof(Char))
			{
				throw new ArgumentException("ConvertOverflow expects a non-character primitive type");
			}

			if (primitiveType == typeof(Single))
			{
				throw new InvalidOperationException("There is no operation for converting to a float with overflow checking");
			}

			if (primitiveType == typeof(Double))
			{
				throw new InvalidOperationException("There is no operation for converting to a double with overflow checking");
			}

			var transitions = CheckConvertible("ConvertOverflow", primitiveType);

			if (primitiveType == typeof(Byte))
			{
				ConvertToByteOverflow(transitions);
				return this;
			}

			if (primitiveType == typeof(sbyte) || primitiveType == typeof(bool)) // bool is an int8 on the stack
			{
				ConvertToSByteOverflow(transitions);
				return this;
			}

			if (primitiveType == typeof(Int16))
			{
				ConvertToInt16Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(UInt16))
			{
				ConvertToUInt16Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(Int32))
			{
				ConvertToInt32Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(UInt32))
			{
				ConvertToUInt32Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(Int64))
			{
				ConvertToInt64Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(UInt64))
			{
				ConvertToUInt64Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(IntPtr))
			{
				ConvertToNativeIntOverflow(transitions);
				return this;
			}

			if (primitiveType == typeof(UIntPtr))
			{
				ConvertToUnsignedNativeIntOverflow(transitions);
				return this;
			}

			throw new Exception("Shouldn't be possible");
		}

		/// <summary>
		/// Convert a value on the stack to the given non-character, non-Single, non-Double primitive type as if it were unsigned.
		/// If the conversion would overflow at runtime, an OverflowException is thrown.
		///
		/// Primitives are int8, uint8, int16, uint16, int32, uint32, int64, uint64, Single, Double, native Int32 (IntPtr), and unsigned native Int32 (UIntPtr).
		/// </summary>
		public Emit<DelegateType> UnsignedConvertOverflow<PrimitiveType>()
		{
			return UnsignedConvertOverflow(typeof(PrimitiveType));
		}

		/// <summary>
		/// Convert a value on the stack to the given non-character, non-Single, non-Double primitive type as if it were unsigned.
		/// If the conversion would overflow at runtime, an OverflowException is thrown.
		///
		/// Primitives are int8, uint8, int16, uint16, int32, uint32, int64, uint64, Single, Double, native Int32 (IntPtr), and unsigned native Int32 (UIntPtr).
		/// </summary>
		public Emit<DelegateType> UnsignedConvertOverflow(Type primitiveType)
		{
			if (primitiveType == null)
			{
				throw new ArgumentNullException("primitiveType");
			}

			if (!primitiveType.IsPrimitive || primitiveType == typeof(Char))
			{
				throw new ArgumentException("UnsignedConvertOverflow expects a non-character primitive type");
			}

			if (primitiveType == typeof(Single))
			{
				throw new InvalidOperationException("There is no operation for converting to a float with overflow checking");
			}

			if (primitiveType == typeof(Double))
			{
				throw new InvalidOperationException("There is no operation for converting to a double with overflow checking");
			}

			var transitions = CheckConvertible("UnsignedConvertOverflow", primitiveType);

			if (primitiveType == typeof(Byte))
			{
				UnsignedConvertToByteOverflow(transitions);
				return this;
			}

			if (primitiveType == typeof(sbyte))
			{
				UnsignedConvertToSByteOverflow(transitions);
				return this;
			}

			if (primitiveType == typeof(Int16))
			{
				UnsignedConvertToInt16Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(UInt16))
			{
				UnsignedConvertToUInt16Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(Int32))
			{
				UnsignedConvertToInt32Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(UInt32))
			{
				UnsignedConvertToUInt32Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(Int64))
			{
				UnsignedConvertToInt64Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(UInt64))
			{
				UnsignedConvertToUInt64Overflow(transitions);
				return this;
			}

			if (primitiveType == typeof(IntPtr))
			{
				UnsignedConvertToNativeIntOverflow(transitions);
				return this;
			}

			if (primitiveType == typeof(UIntPtr))
			{
				UnsignedConvertToUnsignedNativeIntOverflow(transitions);
				return this;
			}

			throw new Exception("Shouldn't be possible");
		}

		/// <summary>
		/// Converts a primitive type on the stack to a Single, as if it were unsigned.
		///
		/// Primitives are int8, uint8, int16, uint16, int32, uint32, int64, uint64, Single, Double, native Int32 (IntPtr), and unsigned native Int32 (UIntPtr).
		/// </summary>
		public Emit<DelegateType> UnsignedConvertToFloat()
		{
			var transitions = CheckConvertible("UnsignedConvertToFloat", typeof(Single));

			UpdateState(OpCodes.Conv_R_Un, transitions);

			return this;
		}

		private void ConvertToNativeInt(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_I, transitions);
		}

		private void ConvertToNativeIntOverflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_I, transitions);
		}

		private void UnsignedConvertToNativeIntOverflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_I_Un, transitions);
		}

		private void UnsignedConvertToUnsignedNativeIntOverflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_U_Un, transitions);
		}

		private void ConvertToUnsignedNativeInt(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_U, transitions);
		}

		private void ConvertToUnsignedNativeIntOverflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_U, transitions);
		}

		private void ConvertToSByte(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_I1, transitions);
		}

		private void ConvertToSByteOverflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_I1, transitions);
		}

		private void UnsignedConvertToSByteOverflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_I1_Un, transitions);
		}

		private void ConvertToInt16(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_I2, transitions);
		}

		private void ConvertToInt16Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_I2, transitions);
		}

		private void UnsignedConvertToInt16Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_I2_Un, transitions);
		}

		private void ConvertToInt32(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_I4, transitions);
		}

		private void ConvertToInt32Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_I4, transitions);
		}

		private void UnsignedConvertToInt32Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_I4_Un, transitions);
		}

		private void ConvertToInt64(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_I8, transitions);
		}

		private void ConvertToInt64Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_I8, transitions);
		}

		private void UnsignedConvertToInt64Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_I8_Un, transitions);
		}

		private void ConvertToFloat(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_R4, transitions);
		}

		private void ConvertToDouble(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_R8, transitions);
		}

		private void ConvertToByte(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_U1, transitions);
		}

		private void ConvertToByteOverflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_U1, transitions);
		}

		private void UnsignedConvertToByteOverflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_U1_Un, transitions);
		}

		private void ConvertToUInt16(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_U2, transitions);
		}

		private void ConvertToUInt16Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_U2, transitions);
		}

		private void UnsignedConvertToUInt16Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_U2_Un, transitions);
		}

		private void ConvertToUInt32(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_U4, transitions);
		}

		private void ConvertToUInt32Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_U4, transitions);
		}

		private void UnsignedConvertToUInt32Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_U4_Un, transitions);
		}

		private void ConvertToUInt64(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_U8, transitions);
		}

		private void ConvertToUInt64Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_U8, transitions);
		}

		private void UnsignedConvertToUInt64Overflow(TransitionWrapper transitions)
		{
			UpdateState(OpCodes.Conv_Ovf_U8_Un, transitions);
		}
	}
}